{{>partial_header}}
package {{packageName}}

import (
	"context"
	"fmt"
	"net/http"
	"strings"
    "golang.org/x/oauth2/clientcredentials"
)

// contextKeys are used to identify the type of value in the context.
// Since these are string, it is possible to get a short description of the
// context key for logging and debugging using key.String().

type contextKey string

func (c contextKey) String() string {
	return "auth " + string(c)
}

var (
	// ContextOAuth2 takes an oauth2.TokenSource as authentication for the request.
	ContextOAuth2 = contextKey("token")

	// ContextBasicAuth takes BasicAuth as authentication for the request.
	ContextBasicAuth = contextKey("basic")

	// ContextAccessToken takes a string oauth2 access token as authentication for the request.
	ContextAccessToken = contextKey("accesstoken")

	// ContextAPIKeys takes a string apikey as authentication for the request
	ContextAPIKeys = contextKey("apiKeys")

	{{#withAWSV4Signature}}
	// ContextAWSv4 takes an Access Key and a Secret Key for signing AWS Signature v4
	ContextAWSv4 = contextKey("awsv4")

	{{/withAWSV4Signature}}
	// ContextHttpSignatureAuth takes HttpSignatureAuth as authentication for the request.
	ContextHttpSignatureAuth = contextKey("httpsignature")

	// ContextServerIndex uses a server configuration from the index.
	ContextServerIndex = contextKey("serverIndex")

	// ContextOperationServerIndices uses a server configuration from the index mapping.
	ContextOperationServerIndices = contextKey("serverOperationIndices")

	// ContextServerVariables overrides a server configuration variables.
	ContextServerVariables = contextKey("serverVariables")

	// ContextOperationServerVariables overrides a server configuration variables using operation specific values.
	ContextOperationServerVariables = contextKey("serverOperationVariables")
)

// BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth
type BasicAuth struct {
	UserName string `json:"userName,omitempty"`
	Password string `json:"password,omitempty"`
}

// APIKey provides API key based authentication to a request passed via context using ContextAPIKey
type APIKey struct {
	Key    string
	Prefix string
}

{{#withAWSV4Signature}}
// AWSv4 provides AWS Signature to a request passed via context using ContextAWSv4
// https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
type AWSv4 struct {
	AccessKey string
	SecretKey string
	SessionToken string
	Region string
	Service string
}

{{/withAWSV4Signature}}
// ServerVariable stores the information about a server variable
type ServerVariable struct {
	Description  string
	DefaultValue string
	EnumValues   []string
}

// ServerConfiguration stores the information about a server
type ServerConfiguration struct {
	URL string
	Description string
	Variables map[string]ServerVariable
}

// ServerConfigurations stores multiple ServerConfiguration items
type ServerConfigurations []ServerConfiguration

{{#withCustomMiddlewareFunction}}
// MiddlewareFunction provides way to implement custom middleware
type MiddlewareFunction func(*http.Request)

{{/withCustomMiddlewareFunction}}
// Configuration stores the configuration of the API client
type Configuration struct {
	Host             string            `json:"host,omitempty"`
	Scheme           string            `json:"scheme,omitempty"`
    TokenUrl    	 string 		   `json:"tokenUrl,omitempty"`
	DefaultHeader    map[string]string `json:"defaultHeader,omitempty"`
	UserAgent        string            `json:"userAgent,omitempty"`
	Debug            bool              `json:"debug,omitempty"`
	Servers          ServerConfigurations
	OperationServers map[string]ServerConfigurations
	HTTPClient       *http.Client
    Context          context.Context
    {{#withCustomMiddlewareFunction}}
	Middleware       MiddlewareFunction
    {{/withCustomMiddlewareFunction}}
}

// NewConfiguration returns a new Configuration object
func NewConfiguration() *Configuration {
	cfg := &Configuration{
		DefaultHeader:    make(map[string]string),
		UserAgent:        "{{{httpUserAgent}}}{{^httpUserAgent}}Konfig/{{{packageVersion}}}/go{{/httpUserAgent}}",
{{#authMethods}}
{{#-first}}
		TokenUrl:   	  "{{{tokenUrl}}}",
{{/-first}}
{{/authMethods}}
		Debug:            false,
        Context: context.Background(),
		{{#servers}}
		{{#-first}}
		Servers:          ServerConfigurations{
		{{/-first}}
			{
				URL: "{{{url}}}",
				Description: "{{{description}}}{{^description}}No description provided{{/description}}",
				{{#variables}}
				{{#-first}}
				Variables: map[string]ServerVariable{
				{{/-first}}
					"{{{name}}}": ServerVariable{
						Description: "{{{description}}}{{^description}}No description provided{{/description}}",
						DefaultValue: "{{{defaultValue}}}",
						{{#enumValues}}
						{{#-first}}
						EnumValues: []string{
						{{/-first}}
							"{{{.}}}",
						{{#-last}}
						},
						{{/-last}}
						{{/enumValues}}
					},
				{{#-last}}
				},
				{{/-last}}
				{{/variables}}
			},
		{{#-last}}
		},
		{{/-last}}
		{{/servers}}
		{{#apiInfo}}
		OperationServers: map[string]ServerConfigurations{
		{{#apis}}
		{{#operations}}
		{{#operation}}
		{{#servers}}
		{{#-first}}
			"{{{classname}}}Service.{{{nickname}}}": {
		{{/-first}}
				{
					URL: "{{{url}}}",
					Description: "{{{description}}}{{^description}}No description provided{{/description}}",
					{{#variables}}
					{{#-first}}
					Variables: map[string]ServerVariable{
					{{/-first}}
						"{{{name}}}": ServerVariable{
							Description: "{{{description}}}{{^description}}No description provided{{/description}}",
							DefaultValue: "{{{defaultValue}}}",
							{{#enumValues}}
							{{#-first}}
							EnumValues: []string{
							{{/-first}}
								"{{{.}}}",
							{{#-last}}
							},
							{{/-last}}
							{{/enumValues}}
						},
					{{#-last}}
					},
					{{/-last}}
					{{/variables}}
				},
		{{#-last}}
			},
		{{/-last}}
		{{/servers}}
		{{/operation}}
		{{/operations}}
		{{/apis}}
		},
		{{/apiInfo}}
	}
	return cfg
}

/*
Configures an API key on Configuration. Ensures multiple calls does not
delete other keys

1. Get the existing map of API keys from the context.
2. If there's no existing map, create a new map.
3. Update the "[API key]"" in the map.
4. Store the updated map back in the context.
*/
func (c *Configuration) AddAPIKey(key string, apiKey APIKey) {
	apiKeys := c.Context.Value(ContextAPIKeys)
	if apiKeys == nil {
		apiKeys = make(map[string]APIKey)
	}
	apiKeys.(map[string]APIKey)[key] = apiKey
	c.Context = context.WithValue(c.Context, ContextAPIKeys, apiKeys)
}

func (c *Configuration) SetHost(host string) {
	if strings.HasPrefix(host, "https://") {
		c.Scheme = "https"
		host = strings.TrimPrefix(host, "https://")
	} else if strings.HasPrefix(host, "http://") {
		c.Scheme = "http"
		host = strings.TrimPrefix(host, "http://")
	} else {
		c.Scheme = ""  // or set a default scheme if desired
	}
	c.Host = host
}

{{#apiKeyMethods}}
func (c *Configuration) Set{{nameInCamelCase}}({{nameInPascalCase}} string) {
	c.AddAPIKey("{{name}}", APIKey{Key: {{nameInPascalCase}}{{#vendorExtensions.x-konfig-prefix}}, Prefix: "{{{.}}}"{{/vendorExtensions.x-konfig-prefix}}})
}

{{/apiKeyMethods}}
{{#clientStateSetterGetterPascalCase}}
func (c *Configuration) Set{{state}}({{state}} string) {
    c.AddAPIKey("{{state}}", APIKey{Key: {{state}}{{#vendorExtensions.x-konfig-prefix}}, Prefix: "{{{.}}}"{{/vendorExtensions.x-konfig-prefix}}})
}
{{/clientStateSetterGetterPascalCase}}
{{#clientStateWithExamplesSetterGetterPascalCase}}
func (c *Configuration) Set{{state}}({{state}} string) {
    c.AddAPIKey("{{state}}", APIKey{Key: {{state}}{{#vendorExtensions.x-konfig-prefix}}, Prefix: "{{{.}}}"{{/vendorExtensions.x-konfig-prefix}}})
}
{{/clientStateWithExamplesSetterGetterPascalCase}}
// Setup OAuth Client Credentials Flow for all requests
func (c *Configuration) SetOAuthClientCredentials(clientId string, clientSecret string) {
       config := &clientcredentials.Config{
               ClientID: clientId,
               ClientSecret: clientSecret,
               TokenURL: c.TokenUrl,
       }
       c.Context = context.WithValue(c.Context, ContextOAuth2, config.TokenSource(c.Context))
}

func (c *Configuration) SetTokenUrl(tokenUrl string) {
	c.TokenUrl = tokenUrl;
}

// AddDefaultHeader adds a new HTTP header to the default header in the request
func (c *Configuration) AddDefaultHeader(key string, value string) {
	c.DefaultHeader[key] = value
}

// URL formats template on a index using given variables
func (sc ServerConfigurations) URL(index int, variables map[string]string) (string, error) {
	if index < 0 || len(sc) <= index {
		return "", fmt.Errorf("index %v out of range %v", index, len(sc)-1)
	}
	server := sc[index]
	url := server.URL

	// go through variables and replace placeholders
	for name, variable := range server.Variables {
		if value, ok := variables[name]; ok {
			found := bool(len(variable.EnumValues) == 0)
			for _, enumValue := range variable.EnumValues {
				if value == enumValue {
					found = true
				}
			}
			if !found {
				return "", fmt.Errorf("the variable %s in the server URL has invalid value %v. Must be %v", name, value, variable.EnumValues)
			}
			url = strings.Replace(url, "{"+name+"}", value, -1)
		} else {
			url = strings.Replace(url, "{"+name+"}", variable.DefaultValue, -1)
		}
	}
	return url, nil
}

// ServerURL returns URL based on server settings
func (c *Configuration) ServerURL(index int, variables map[string]string) (string, error) {
	return c.Servers.URL(index, variables)
}

func getServerIndex(ctx context.Context) (int, error) {
	si := ctx.Value(ContextServerIndex)
	if si != nil {
		if index, ok := si.(int); ok {
			return index, nil
		}
		return 0, reportError("Invalid type %T should be int", si)
	}
	return 0, nil
}

func getServerOperationIndex(ctx context.Context, endpoint string) (int, error) {
	osi := ctx.Value(ContextOperationServerIndices)
	if osi != nil {
		if operationIndices, ok := osi.(map[string]int); !ok {
			return 0, reportError("Invalid type %T should be map[string]int", osi)
		} else {
			index, ok := operationIndices[endpoint]
			if ok {
				return index, nil
			}
		}
	}
	return getServerIndex(ctx)
}

func getServerVariables(ctx context.Context) (map[string]string, error) {
	sv := ctx.Value(ContextServerVariables)
	if sv != nil {
		if variables, ok := sv.(map[string]string); ok {
			return variables, nil
		}
		return nil, reportError("ctx value of ContextServerVariables has invalid type %T should be map[string]string", sv)
	}
	return nil, nil
}

func getServerOperationVariables(ctx context.Context, endpoint string) (map[string]string, error) {
	osv := ctx.Value(ContextOperationServerVariables)
	if osv != nil {
		if operationVariables, ok := osv.(map[string]map[string]string); !ok {
			return nil, reportError("ctx value of ContextOperationServerVariables has invalid type %T should be map[string]map[string]string", osv)
		} else {
			variables, ok := operationVariables[endpoint]
			if ok {
				return variables, nil
			}
		}
	}
	return getServerVariables(ctx)
}

// ServerURLWithContext returns a new server URL given an endpoint
func (c *Configuration) ServerURLWithContext(ctx context.Context, endpoint string) (string, error) {
	sc, ok := c.OperationServers[endpoint]
	if !ok {
		sc = c.Servers
	}

	if ctx == nil {
		return sc.URL(0, nil)
	}

	index, err := getServerOperationIndex(ctx, endpoint)
	if err != nil {
		return "", err
	}

	variables, err := getServerOperationVariables(ctx, endpoint)
	if err != nil {
		return "", err
	}

	return sc.URL(index, variables)
}
